<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<div th:replace="components/layout/header :: head(~{::title}, ~{}, ~{})">
    <title th:text="${global.siteInfo.websiteName}">
        派聪明 | 技术派
    </title>
</div>

<link rel="stylesheet" href="/css/views/chat-home.css" th:href="${global.siteInfo.oss + '/css/views/chat-home.css'}"/>

<script src="/js/stomp.min.js" th:src="${global.siteInfo.oss + '/js/stomp.min.js'}"></script>

<body id="body" class="bg-color">
<!-- 导航栏 -->
<div th:replace="components/layout/navbar :: navbar"></div>
<div class="custom-home">
    <div class="chat-wrap">
        <div class="chat-sidebar">
            <!-- 侧边栏 -->
            <div th:replace="views/chat-home/sidebar/index.html"></div>
        </div>
        <div class="chat-main">
            <div class="window-header">
                <div class="window-header-title">
                    <div class="name">
                        <div class="window-header-main-title home_chat-body-title__5S8w4"
                             th:data-target="${!global.isLogin ? '#loginModal' : (global.user.starStatus.code == -1 ? '#registerModal' : '')}"
                             th:data-toggle="${!global.isLogin || global.user.starStatus.code == -1 ? 'modal' : ''}">
                            点击登录，体验派聪明AI助手
                        </div>
                        <div class="chat-annotation" th:if="${global.user != null}">
                            <div th:switch="${global.user.starStatus.code}">
                                <a th:case="-1"
                                   href="#"
                                   class="annotation"
                                   data-target="#registerModal"
                                   data-toggle="modal"
                                >绑定二哥编程星球，提升每天对话次数</a>
                                <span th:case="0" class="annotation">审核中</span>
                                <span th:case="1" class="annotation">试用中，添加管理员微信 itwanger 催审核</span>
                                <div class="c-bubble-trigger com-verification" th:case="2">
                                    <i class="verified"></i>
                                </div>
                            </div>
                        </div>

                    </div>

                    <div class="window-header-sub-title">与派聪明的 <span id="chatCnt">0</span> 条对话
                        <span class="info">以天为单位，无限期重置）</span></div>
                </div>
                <!-- 加一个下拉框，选项是 OpenAI 讯飞星火 技术派 DeepSeek 通义千问 智谱清言 豆包-->
                <div class="chat-type">
                    <select class="styled-dropdown" id="chat-type">
                        <option value="DEEP_SEEK">DeepSeek</option>
                        <option value="CHAT_GPT_3_5">OpenAI</option>
                        <option value="ALI_AI">通义千问</option>
                        <option value="XUN_FEI_AI">讯飞星火</option>
                        <option value="ZHI_PU_AI">智谱</option>
                        <option value="DOU_BAO_AI">豆包</option>
                        <option value="PAI_AI" selected>技术派</option>
                    </select>
                </div>
            </div>
            <div class="message-content" id="chat-content">
            </div>

            <div class="chat-input">
                <textarea id="input-field" class="form-control" rows="3" placeholder="你好，我是派聪明，快登录和我对线吧" disabled></textarea>
                <button id="send-btn" disabled>
                    <div class="button_icon-button-icon__qlUH3 no-dark">
                        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none"><defs><path id="send-white_svg__a" d="M0 0h16v16H0z"></path></defs><g><mask id="send-white_svg__b" fill="#fff"><use xlink:href="#send-white_svg__a"></use></mask><g mask="url(#send-white_svg__b)"><path transform="translate(1.333 2)" d="M0 4.71 6.67 6l1.67 6.67L12.67 0 0 4.71Z" style="stroke: rgb(255, 255, 255); stroke-width: 1.33333; stroke-opacity: 1; stroke-dasharray: 0, 0;"></path><path transform="translate(8.003 6.117)" d="M0 1.89 1.89 0" style="stroke: rgb(255, 255, 255); stroke-width: 1.33333; stroke-opacity: 1; stroke-dasharray: 0, 0;"></path></g></g></svg>
                    </div>
                    <div class="button_icon-button-text__k3vob">等待登录</div>
                </button>
            </div>
        </div>
    </div>
    <!-- 底部信息 -->
    <div th:replace="components/layout/footer :: footer"></div>
</div>
</body>


<script th:inline="javascript">
    const inputField = $("#input-field");
    const sendBtn = $("#send-btn"), sendBtnText = $(".button_icon-button-text__k3vob");
    const chatContent = $("#chat-content");
    const promptField = $("#promptField");
    let stompClient = null;
    // 用户名
    const chatTitle = $(".window-header-main-title");
    let wsConnected = false;

    // 从 global.user.photo 中取出用户头像 thymeleaf 传入的值
    const isLogin = [[${global.isLogin}]], user = [[${global.user}]];


    // 页面加载完成后，执行 initWs
    $(function () {
        if (isLogin) {
            initWs();
        } else {
            console.log("请先登录");
        }
    });

    // 建立会话, 首先判断一下用户是否已经登录
    const session = getCookie("f-session");

    $('#chat-type').change(function(e) {
        // 更新了选中的下拉框时，重新建立连接
        if (isLogin) {
            chatContent.html('');
            disconnect();
            initWs();

            // 刷新对话历史记录
            listChatSessions();
        } else {
            console.log("请先登录");
        }
    });

    function initWs() {
        let protocol = window.location.protocol.replace("http", "ws");
        let host = window.location.host;
        let aiType = $('#chat-type').val();
        console.log("AITYPE = ", aiType);

        let socket = new WebSocket(`${protocol}//${host}/gpt/${session}/${aiType}`);
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function (frame) {
            console.log('ws连接成功: ' + frame);
            wsConnected = true;
            inputField.removeAttr("disabled");
            // 改变 inputField 的 placeholder
            inputField.val("");
            inputField.attr("placeholder", "可按回车发送");
            sendBtnText.text("发送");
            sendBtn.removeAttr("disabled");
            // 改变 chatTitle 的内容
            chatTitle.text(user.userName);


            stompClient.subscribe(`/user/chat/rsp`, function (message) {
                // 表示这个长连接，订阅了 "/chat/rsp" , 这样后端像这个路径转发消息时，我们就可以拿到对应的返回
                // 解析 JSON 字符串
                console.log("rsp:", message);
                let res = JSON.parse(message.body);
                console.log("res:", res);

                // 记录聊天次数
                $("#chatCnt").html(` ${res.usedCnt}/${res.maxCnt} `);

                const data = res.records;

                // 把 home_chat-message-actions__loading 的元素移除，不再显示 loading
                $(".home_chat-message-actions__loading").remove();

                if (data.length > 1) {
                    // 返回历史全部信息
                    chatContent.html('');
                    for (let i = data.length - 1; i >= 0; i--) {
                        if (data[i].question) {
                            addClientMsg(data[i].question, false);
                        }
                        if (i == 0) {
                            addSplit();
                        }
                        appendServerMessage(data[i]);
                    }
                } else {
                    appendServerMessage(data[0]);
                }

                // 添加完消息后，除了流式持续返回这种场景，其他的恢复按钮的状态
                if(data[data.length - 1].answerType != 'STREAM') {
                    sendBtn.removeAttr("disabled");
                }
            });

            // 连接建立成功之后，自动同步历史消息；这里主要是避免ws还没有建立成功，用户切换了会话，从而导致拿不到最新会话的历史记录这个问题
            loadChat(null);
        });

        // 关闭链接
        socket.onclose = disconnect;
    }

    function disconnect() {
        if (stompClient !== null) {
            stompClient.disconnect();
        }
        console.log("ws中断");
        wsConnected = false;
        stompClient = null;
        // 提醒用户重新连接
        inputField.attr("disabled", "disabled").val("连接中断，点击右侧按钮重连");
        sendBtn.removeAttr("disabled");

        sendBtnText.text("重连");
    }

    function escapeHtml(unsafe) {
        return unsafe
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }



    // 添加服务器端消息
    function appendServerMessage(answer) {
        let content = answer.answer;
        let time = answer.answerTime;
        let answerType = answer.answerType;
        let chatId = answer.chatUid
        let appendLastChat = false;
        console.log("准备添加内容:", answer);
        // 如果 source 等于"CHAT_GPT_3_5"
        if("JSON" === answerType) {
            // 需要对 body 的 JSON 字符串进行解析
            const res = JSON.parse(content);
            console.log(" res:", res);
            if (res.length === 1) {
                content = res[0].message.content;
                console.log(" content escapeHtml before:", content);
                // content = prettyCode(content);
                content = converter.makeHtml(content);
                console.log(" content escapeHtml after:", content);
            } else {
                // 直接返回的结果
                console.log(" content escapeHtml before:", res);
                // content = prettyCode(res);
                content = converter.makeHtml(content);
                console.log(" content escapeHtml after:", content);
            }
        } else if ('STREAM' === answerType || 'STREAM_END' === answerType) {
            const lastDiv = $(`#${chatId}`)
            console.log("流式结果返回: ", content, lastDiv);
            content = converter.makeHtml(content);
            // content = prettyCode(content.replaceAll('$…$', ''));
            console.log("流式结果转 HTML: ", content)

            if (lastDiv.length > 0) {
                // 对于流式返回的结果，找上一次的返回，进行结果的追加，手动将分隔符给干掉
                lastDiv.html(content);
                appendLastChat = true;
            } else{
                // 上一次没有输出过，则格式化文本，重新输出
                // content = prettyCode(content.replaceAll('$…$', ''));
            }
        }

        if(!appendLastChat) {
            let serverMsg = `
            <div class="home_chat-message__rdH_g server-msg">
                <div class="home_chat-message-container__plj_e">
                    <div class="home_chat-message-avatar__611lI">
                        <div class="no-dark">
                            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="35" height="35" fill="none">
                                <defs><path id="bot_svg__a" d="M0 0h30v30H0z"></path><path id="bot_svg__c" d="M0 0h20.455v20.455H0z"></path></defs><g><rect fill="var(--pai-bg-normal-1)" width="30" height="30" rx="10"></rect><mask id="bot_svg__b" fill="#fff"><use xlink:href="#bot_svg__a"></use></mask><g mask="url(#bot_svg__b)"><g transform="translate(4.773 4.773)"><mask id="bot_svg__d" fill="#fff"><use xlink:href="#bot_svg__c"></use></mask><g mask="url(#bot_svg__d)"><path fill-rule="evenodd" d="M19.11 8.37c.17-.52.26-1.06.26-1.61 0-.9-.24-1.79-.71-2.57a5.24 5.24 0 0 0-4.53-2.59c-.37 0-.73.04-1.09.11A5.201 5.201 0 0 0 9.17 0h-.04C6.86 0 4.86 1.44 4.16 3.57A5.11 5.11 0 0 0 .71 6.04C.24 6.83 0 7.72 0 8.63c0 1.27.48 2.51 1.35 3.45-.18.52-.27 1.07-.27 1.61 0 .91.25 1.8.71 2.58 1.13 1.94 3.41 2.94 5.63 2.47a5.18 5.18 0 0 0 3.86 1.71h.05c2.26 0 4.27-1.44 4.97-3.57a5.132 5.132 0 0 0 3.45-2.47c.46-.78.7-1.67.7-2.58 0-1.28-.48-2.51-1.34-3.46ZM8.947 18.158c-.04.03-.08.05-.12.07.7.58 1.57.89 2.48.89h.01c2.14 0 3.88-1.72 3.88-3.83v-4.76c0-.02-.02-.04-.04-.05l-1.74-.99v5.75c0 .23-.13.45-.34.57l-4.13 2.35Zm-.67-1.153 4.17-2.38c.02-.01.03-.03.03-.05v-1.99l-5.04 2.87c-.21.12-.47.12-.68 0l-4.13-2.35c-.04-.02-.09-.06-.12-.07-.04.21-.06.43-.06.65 0 .67.18 1.33.52 1.92v-.01c.7 1.19 1.98 1.92 3.37 1.92.68 0 1.35-.18 1.94-.51ZM3.903 5.168v-.14c-.85.31-1.57.9-2.02 1.68a3.78 3.78 0 0 0-.52 1.91c0 1.37.74 2.64 1.94 3.33l4.17 2.37c.02.01.04.01.06 0l1.75-1-5.04-2.87a.64.64 0 0 1-.34-.57v-4.71Zm13.253 3.337-4.18-2.38c-.02 0-.04 0-.06.01l-1.74.99 5.04 2.87c.21.12.34.34.34.58v4.85c1.52-.56 2.54-1.99 2.54-3.6 0-1.37-.74-2.63-1.94-3.32ZM8.014 5.83c-.02.01-.03.03-.03.05v1.99L13.024 5a.692.692 0 0 1 .68 0l4.13 2.35c.04.02.08.05.12.07.03-.21.05-.43.05-.65 0-2.11-1.74-3.83-3.88-3.83-.68 0-1.35.18-1.94.51l-4.17 2.38Zm1.133-4.492c-2.15 0-3.89 1.72-3.89 3.83v4.76c0 .02.02.03.03.04l1.75 1v-5.75c0-.23.13-.45.34-.57l4.13-2.35c.04-.03.09-.06.12-.07-.7-.58-1.58-.89-2.48-.89ZM7.983 11.51l2.24 1.27 2.25-1.27V8.95l-2.25-1.28-2.24 1.28v2.56Z" style="fill: var(--pai-brand-1-normal);"></path></g></g></g></g>
                            </svg>
                        </div>
                    </div>
                    <div class="home_chat-message-item__hDEOq">
                        <div class="home_chat-message-top-actions__PfOzb">
                            <div class="home_chat-message-top-action__wXKmA">复制</div>
                        </div>
                        <div class="markdown-body">
                            <p id="${chatId}">${content}</p>
                        </div>
                    </div>
                    <div class="home_chat-message-actions__nrHd1">
                        <div class="home_chat-message-action-date__6ToUp">${time}</div>
                    </div>
                </div>
            </div>
            `;

            chatContent.append(serverMsg);
        }
        // 高亮代码
        chatContent.find('pre code').each(function(i, block) {
            hljs.highlightBlock(block);
        });

        // 添加完后滚动到底部
        scrollToBottom();

        copy();

        // katex 渲染
        katexRender(chatContent[0]);
    }


    // 复制功能
    function copy() {
        // 从 chatContent 中获取最后一个 chat-message
        const chatMessage = chatContent.children(".home_chat-message__rdH_g").last();
        console.log("chatContent", chatMessage);
        // 从 chatMessage 找出复制按钮
        const copyBtn = chatMessage.find(".home_chat-message-top-action__wXKmA").get(0);

        const clipboard = new ClipboardJS(copyBtn, {
            text: function(trigger) {
                let copyInput = chatMessage.find('.markdown-body').get(0);
                return copyInput.innerText;
            }
        });

        clipboard.on('success', function(e) {
            // 复制成功
            toastr.info("复制成功");
            e.clearSelection();
        });

        clipboard.on('error', function(e) {
            console.log('复制失败');
        });
    }

    // 添加用户端消息
    function addClientMsg(messageContent, showLoading) {
        if (messageContent.startsWith('prompt-')) {
            // 移除提示词标识
            messageContent = messageContent.substring(7);
        }

        const avatarUrl = user.photo;
        let loadingDiv = `
        <div class="home_chat-message-actions__loading">
            <div class="home_chat-message-action-loading__6ToUp">
                <div class="lds-ellipsis">
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                </div>
            </div>
        </div>
    `;

        let userMsg = `
        <div class="home_chat-message-user__WsuiB">
            <div class="home_chat-message-container__plj_e">
                <div class="home_chat-message-avatar__611lI">
                    <div class="user-avatar">
                        <img src="${avatarUrl}" alt="smiley" class="__EmojiPicker__ epr-emoji-img" loading="eager">
                    </div>
                </div>
                <div class="home_chat-message-item__hDEOq">
                    <div class="markdown-body">
                        <p>${messageContent}</p>
                    </div>
                </div>
                ${showLoading ? loadingDiv : ''}
            </div>
        </div>
    `;

        chatContent.append(userMsg);
        // 添加完后滚动到底部
        scrollToBottom();
    }

    function addSplit() {
        let userMsg = `<div class="chat_split_hr"><span class="chat_split_txt">&nbsp;&nbsp;历史聊天分割线&nbsp;&nbsp;</span></div>`;
        chatContent.append(userMsg);
        // 添加完后滚动到底部
        scrollToBottom();
    }

    // 滚动到底部
    function scrollToBottom() {
        chatContent.scrollTop(chatContent[0].scrollHeight);
    }

    function doSend() {
        let qa = inputField.val();
        // const prompt = promptField.val() | "";
        if (qa.length > 512) {
            toastr.info("提问长度请不要超过512字符哦~");
            return;
        }
        // 表示将消息转发到那个目标，类似与http请求中的path路径
        // qa = prompt == "" ? qa : prompt + "prompt-" + qa;
        if (lastChatId == null) {
            // 创建新的会话
            lastChatId = newChat();
        }
        stompClient.send("/app/chat/" + session + "/" + lastChatId, {'s-uid': session}, qa);
        // 清空 textarea
        inputField.val("");

        addClientMsg(qa.replace(/\n/g, "<br/>"), true);

        // 将 button 设为禁用，防止用户连续点击
        sendBtn.attr("disabled", true);
    }


    sendBtn.click(function () {
        if (stompClient == null) {
            initWs();
        } else {
            // 如果消息内容为空的时候重新聚焦到输入框
            if (inputField.val() === "") {
                inputField.focus();
            } else {
                // 发送消息
                doSend();
            }
        }
    });

    promptField.keydown(function (e) {
        if (isLogin && e.key === 'Enter') {
            // 输入提示词并回车的场景
            let prompt = document.getElementById("promptField").value;
            prompt = prompt.trim();
            if (!prompt) {
                // 空的提示词，不做任何处理
                return;
            }

            let qa = 'prompt-' + prompt;
            if (qa.length > 512) {
                toastr.info("提问长度请不要超过512字符哦~");
                return;
            }
            if (lastChatId == null) {
                // 创建新的会话
                lastChatId = newChat();
                toastr.info("对话已创建，请重新确认提示词吧~");
            } else {
                stompClient.send("/app/chat/" + session + "/" + lastChatId, {'s-uid': session}, qa);
                // 清空 textarea
                inputField.val("");

                addClientMsg(prompt.replace(/\n/g, "<br/>"), true);

                // 将 button 设为禁用，防止用户连续点击
                sendBtn.attr("disabled", true);

                // 清空输入内容
                promptField.val("");
            }
        }
    });

    inputField.keydown(function (e) {
        if (e.keyCode === 13) {
            // 按下回车的时候调用 sendbtn 的 click 事件
            sendBtn.click();
            // 阻止默认行为
            e.preventDefault();
        }
    });
</script>
</html>
